﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Web;
using System.Web.UI;
using NativeCode.Core.Extensions;

namespace NativeCode.Web.Controls.Renderers
{
	public class HtmlWriter
	{
		#region Delegates

		public delegate void HtmlWriterHandler(HtmlWriter writer);

		public delegate void HtmlWriterHandler<T1>(HtmlWriter writer, T1 t1);

		public delegate void HtmlWriterHandler<T1, T2>(HtmlWriter writer, T1 t1, T2 t2);

		public delegate void HtmlWriterHandler<T1, T2, T3>(HtmlWriter writer, T1 t1, T2 t2, T3 t3);

		#endregion

		public HtmlWriter(HtmlTextWriter writer)
		{
			CurrentCustomTagAttributes = new Dictionary<string, string>();
			CurrentTagAttributes = new Dictionary<HtmlTextWriterAttribute, string>();
			CurrentTagPath = new List<string>();
			CurrentText = new List<string>();
			Writer = writer;
		}

		#region Properties

		protected string CurrentCustomTag { get; set; }
		protected Dictionary<string, string> CurrentCustomTagAttributes { get; set; }

		protected HtmlTextWriterTag? CurrentTag { get; set; }
		protected Dictionary<HtmlTextWriterAttribute, string> CurrentTagAttributes { get; set; }

		protected List<string> CurrentTagPath { get; set; }
		protected List<string> CurrentText { get; set; }

		protected bool CanFlush { get; set; }
		protected bool IgnoreUntilTagEnd { get; set; }

		public HtmlTextWriter Writer { get; private set; }

		#endregion

		#region Fluent Methods

		public HtmlWriter Tag(HtmlTextWriterTag tag)
		{
			if (IgnoreUntilTagEnd)
			{
				return this;
			}

			if (CanFlush)
			{
				Flush();
			}

			CanFlush = true;
			CurrentTag = tag;
			CurrentTagPath.Add(tag.ToString().ToLower());
			return this;
		}

		public HtmlWriter Tag(string tagname)
		{
			if (IgnoreUntilTagEnd)
			{
				return this;
			}

			if (CanFlush)
			{
				Flush();
			}

			CanFlush = true;
			CurrentCustomTag = tagname;
			CurrentTagPath.Add(tagname.ToLower());
			return this;
		}

		public HtmlWriter Attribute(HtmlTextWriterAttribute attribute, string value)
		{
			if (IgnoreUntilTagEnd)
			{
				return this;
			}

			if (!CurrentTagAttributes.ContainsKey(attribute))
			{
				CurrentTagAttributes.Add(attribute, value);
			}
			return this;
		}

		public HtmlWriter Attribute(string name, string value)
		{
			if (IgnoreUntilTagEnd)
			{
				return this;
			}

			if (!CurrentCustomTagAttributes.ContainsKey(name))
			{
				CurrentCustomTagAttributes.Add(name, value);
			}
			return this;
		}

		/// <remark>
		/// This is named so that there is no collision with the Tag method. It's annoying
		/// when VS's code complete tries to fill in the method as Tag instead of TagEnd.
		/// </remark>
		/// <returns></returns>
		public HtmlWriter EndTag()
		{
			if (IgnoreUntilTagEnd)
			{
				IgnoreUntilTagEnd = false;
				return this;
			}

			if (CurrentTagPath.Count > 0)
			{
				Flush();
				UnwindLast();
			}

			return this;
		}

		public void Close()
		{
			UnwindAll();
		}

		#region Call with all of it's retarded incantations...

		public HtmlWriter Call(HtmlWriterHandler handler)
		{
			if (IgnoreUntilTagEnd)
			{
				return this;
			}

			if (handler != null)
			{
				Flush();
				handler(new HtmlWriter(Writer));
			}

			return this;
		}

		public HtmlWriter Call<T1>(HtmlWriterHandler<T1> handler, T1 t1)
		{
			if (IgnoreUntilTagEnd)
			{
				return this;
			}

			if (handler != null)
			{
				Flush();
				handler(new HtmlWriter(Writer), t1);
			}

			return this;
		}

		public HtmlWriter Call<T1, T2>(HtmlWriterHandler<T1, T2> handler, T1 t1, T2 t2)
		{
			if (IgnoreUntilTagEnd)
			{
				return this;
			}

			if (handler != null)
			{
				Flush();
				handler(new HtmlWriter(Writer), t1, t2);
			}

			return this;
		}

		public HtmlWriter Call<T1, T2, T3>(HtmlWriterHandler<T1, T2, T3> handler, T1 t1, T2 t2, T3 t3)
		{
			if (IgnoreUntilTagEnd)
			{
				return this;
			}

			if (handler != null)
			{
				Flush();
				handler(new HtmlWriter(Writer), t1, t2, t3);
			}

			return this;
		}

		#endregion

		#region Write

		public HtmlWriter Write(string text, bool encode)
		{
			if (IgnoreUntilTagEnd)
			{
				return this;
			}

			CurrentText.Add(encode ? HttpUtility.HtmlEncode(text) : text);
			return this;
		}

		public HtmlWriter Write(string text)
		{
			if (IgnoreUntilTagEnd)
			{
				return this;
			}

			return Write(text, false);
		}

		public HtmlWriter WriteFormat(string format, params object[] values)
		{
			if (IgnoreUntilTagEnd)
			{
				return this;
			}

			return Write(string.Format(format, values));
		}

		#endregion

		#region Conditionals

		public HtmlWriter AttributeIf(HtmlTextWriterAttribute attribute, string value, bool condition)
		{
			return condition ? Attribute(attribute, value) : this;
		}

		public HtmlWriter AttributeIf(string name, string value, bool condition)
		{
			return condition ? Attribute(name, value) : this;
		}

		public HtmlWriter TagIf(HtmlTextWriterTag tag, bool condition)
		{
			IgnoreUntilTagEnd = !condition;
			return Tag(tag);
		}

		public HtmlWriter TagIf(string tagname, bool condition)
		{
			IgnoreUntilTagEnd = !condition;
			return Tag(tagname);
		}

		public HtmlWriter WriteIf(string text, bool condition)
		{
			return condition ? Write(text) : this;
		}

		public HtmlWriter WriteFormatIf(string format, bool condition, params object[] values)
		{
			return condition ? WriteFormat(format, values) : this;
		}

		#endregion

		#region Helpers

		protected HtmlWriter Flush()
		{
			if (CanFlush)
			{
				foreach (KeyValuePair<HtmlTextWriterAttribute, string> attribute in CurrentTagAttributes)
				{
					Writer.AddAttribute(attribute.Key, attribute.Value);
				}

				foreach (KeyValuePair<string, string> attribute in CurrentCustomTagAttributes)
				{
					Writer.AddAttribute(attribute.Key, attribute.Value);
				}

				if (CurrentTag.HasValue)
				{
					Writer.RenderBeginTag(CurrentTag.Value);
#if DEBUG
					Debug.WriteLine(string.Format("{0} <{1}>", "\t".Times(Writer.Indent), CurrentTag).ToLower());
#endif
				}
				else
				{
					Writer.RenderBeginTag(CurrentCustomTag);
#if DEBUG
					Debug.WriteLine(string.Format("{0} <{1}>", "\t".Times(Writer.Indent), CurrentCustomTag).ToLower());
#endif
				}

				foreach (string text in CurrentText)
				{
					Writer.Write(text);
				}

				CurrentText.Clear();
				CurrentTagAttributes.Clear();
				CurrentCustomTagAttributes.Clear();
				CurrentTag = default(HtmlTextWriterTag?);
				CurrentCustomTag = null;

				CanFlush = false;
				IgnoreUntilTagEnd = false;
			}

			return this;
		}

		protected HtmlWriter UnwindAll()
		{
			int count = CurrentTagPath.Count;
			for (int i = count; 0 < i; i--)
			{
				Writer.RenderEndTag();
#if DEBUG
				Debug.WriteLine(string.Format("{0} </{1}>", "\t".Times(Writer.Indent), CurrentTagPath[i]).ToLower());
#endif
			}

			return this;
		}

		protected HtmlWriter UnwindLast()
		{
#if DEBUG
			Debug.WriteLine(string.Format("{0} </{1}>", "\t".Times(Writer.Indent), CurrentTagPath[CurrentTagPath.Count - 1]).ToLower());
#endif
			CurrentTagPath.RemoveAt(CurrentTagPath.Count - 1);
			Writer.RenderEndTag();

			return this;
		}

		#endregion

		#endregion

		public override string ToString()
		{
			return string.Format("Tag: {0}, Path: {1}, Ignore: {2}",
			                     string.IsNullOrEmpty(CurrentCustomTag) ? CurrentTag.ToString() : CurrentCustomTag,
			                     string.Join(" > ", CurrentTagPath.ToArray()),
			                     IgnoreUntilTagEnd);
		}
	}
}